Hide tabs based on user authorization levels#3035
Conversation
Review fixes for user authorization tab hiding (#3035)
Map each gated UI capability to the ServiceControl HTTP route it represents (apiRoutes.ts), and normalize a (method, path) pair into a comparison key with route parameters collapsed to {} (routeMatching.ts). Matching couples only to method and path structure, so it survives server-side route parameter renames.
Co-Authored-By: Dennis van der Stelt <dvdstelt@gmail.com>
Co-Authored-By: williambza <william.brander@gmail.com>
Add AllowedRoutesStore, which fetches the my/routes manifest from the Primary and Monitoring instances, merges them, and dedupes concurrent fetches. Add the useAllowedRoutes composable (canCall / canAnyCall) that resource-owning view models build on, plus the startup fetch in App.vue. Gating is fail-open until the user is authenticated and the manifest has loaded, but a malformed manifest entry is skipped rather than aborting the load, so a single bad entry can never leave the store unloaded and silently fail every gate open. Co-Authored-By: Dennis van der Stelt <dvdstelt@gmail.com> Co-Authored-By: williambza <william.brander@gmail.com>
Hide navigation items (PageHeader.vue) and Configuration sub-tabs (ConfigurationView.vue) whose backing ServiceControl route the current user is not allowed to call. Co-Authored-By: Ramon Smits <ramon.smits@gmail.com> Co-Authored-By: Dennis van der Stelt <dvdstelt@gmail.com>
Add PermissionGate, a wrapper that disables a slotted action and shows a tooltip with the reason when the user lacks the backing route, instead of hiding the control. Apply it to failed-message, deleted-message and message-group actions, custom-check dismiss, heartbeat endpoint instances, redirects, health-check notifications, edit and retry, and monitored endpoint deletion. Resource-owning stores expose the capability getters these controls bind to. The edit/config fetch is skipped for users without edit permission and treats a 403 as unavailable rather than logging an error. Co-Authored-By: Ramon Smits <ramon.smits@gmail.com> Co-Authored-By: williambza <william.brander@gmail.com>
Render a Configuration page that lists the capabilities derived from the current user's allowed routes, with the supporting route and router-link entries. Co-Authored-By: Ramon Smits <ramon.smits@gmail.com> Co-Authored-By: williambza <william.brander@gmail.com>
| export const groups = [ | ||
| { | ||
| area: "Failed messages", | ||
| caps: [ |
There was a problem hiding this comment.
What does caps stand for/mean?
There was a problem hiding this comment.
caps is short for capabilities, the individual actions listed under each area on the User Permissions page.
Each area (e.g. "Recoverability groups") has a caps array where every entry pairs a human-readable label with the ref to the ServiceControl API route that action calls, taken from the ApiRoutes registry:
{ label: "Retry group", ref: ApiRoutes.retryGroup }
At render time the page maps each capability through canCall(ref), which checks whether that route is in the user's my/routes manifest, and shows a ✓ or ✗ accordingly. So caps is just the per-area list of "things you might be able to do here," and the table resolves each one to allowed/not-allowed for the current user.
There was a problem hiding this comment.
We could rename it or add a comment.
Summary
Gates the ServicePulse UI on the set of ServiceControl API routes the signed-in user is actually allowed to call (the
my/routesmanifest). Navigation items and Configuration sub-tabs the user cannot reach are hidden; action buttons they cannot use are disabled with an explanatory tooltip (not hidden), so the UI stays discoverable.Gating is fail-open: it only applies once authorization is enabled, the user is authenticated, and the manifest has loaded. In every other state the full UI is shown, so an unreachable or slow ServiceControl never locks a user out.
This PR consolidates the work previously split across #3041 and #3044. It supersedes the earlier permission-string gating model, which was introduced and then removed on this branch in favour of the route-based approach.
How it works
composables/apiRoutes.ts): maps each gated UI capability to the ServiceControl HTTP route it represents. This is the single point of coupling to ServiceControl's route surface; the UI gates on routes it already calls, never on internal permission names.composables/routeMatching.ts): normalizes a (method, path) pair into a comparison key with route parameters collapsed to{}, so matching couples to method plus path structure and survives server-side parameter renames.stores/AllowedRoutesStore.ts): fetchesmy/routesfrom the Primary and Monitoring instances, merges them, and keeps a per-store in-flight guard so concurrent callers share one request. Per-instance fail-open: a failed or non-array response is ignored rather than fataled.composables/useAllowedRoutes.ts): exposescanCallandcanAnyCall. Resource-owning stores call these and surface capability getters; templates bind to those getters.components/PermissionGate.vue): reusable component that disables a slotted action and shows a tooltip with the reason when the user lacks the route, instead of removing the control.What is gated
PageHeader.vue) and Configuration sub-tabs(
ConfigurationView.vue): hidden when no backing route is allowed.HealthChecksStore,HeartbeatInstancesStore,MessageStore,MonitoringEndpointDetailsStore,RedirectsStore,ThroughputStore.UserPermissions.vue) renders the capabilities derived from the user's allowed routes.Design decisions
Backend dependency
Requires ServiceControl to serve the
my/routesallowed-route manifest on thePrimary instance, and the equivalent on the Monitoring instance.
Reviewer Checklist